/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License.You may obtain a copy of the License at
	http://www.mozilla.org/MPL/MPL-1.1.html
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/
 
import com.bourre.utils.ClassUtils;

import fever.log.FeverDebug;
import fever.utils.Random;
 
/**
 * Provides array utilities methods.
 * 
 * @author Romain Ecarnot
 */
class fever.utils.ArrayUtil
{
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * Indicates if {@code array} contains passed-in {@code value}.
	 * 
	 * @param value Value to search
	 * @param array {@code Array} instance to search in
	 * @return {@code true} if passed-in {@code value} is in array.
	 */
	public static function contains( value, array : Array ) : Boolean
	{
		var l : Number = array.length;
		
		while( --l > -1 ) if( value == array[l] ) return true;
		return false;
	}
	
	/**
	 * Indicates if {@code array} contains passed-in {@code value} instance type.
	 * 
	 * <p>Don't use it to test primitive type as we compare full qualified class name.
	 * 
	 * @param value Value to search
	 * @param array {@code Array} instance to search in
	 * @return {@code true} if passed-in {@code value} is in array.
	 */
	public static function containsType( value, array : Array ) : Boolean
	{
		var l : Number = array.length;
		var tType : String;
		
		while( --l > -1 )
		{
			if( ClassUtils.getFullyQualifiedClassName( value ) 
				== 
				ClassUtils.getFullyQualifiedClassName( array[l] ) ) 
					return true;
		}
		return false;
	}
	
	/**
	 * Remove passed-in {@code value} value from passed-in {@code array} array
	 * 
	 * <p>All occurances of {@code value} are removed.
	 * 
	 * @param value Element to remove
	 * @param array {@code Array} instance to search in
	 * @param flag ( optional ) {@code true} to return list of removed index in array, 
	 * {@code false} to return source array.
	 * @return {@code true} is passed-in {@code value} is removed
	 */
	static public function remove( value, array : Array, flag : Boolean ) : Array
	{
		var l : Number = array.length;
		var a : Array = new Array();
		
		while( --l > -1 )
		{
			if( value === array[l] )
			{
				a.unshift( l );
				array.splice( l, 1 );
			}
		}
		return ( flag ) ? a : array;
	}
	
	/**
	 * Removes element at {@code index} position from passed-in
	 * {@code array} array.
	 * 
	 * @param index Index to remove
	 * @param array {@code Array} instance to search in
	 * @param flag ( optional ) {@code true} to return removed element, 
	 * {@code false} to return source array.
	 */
	public static function removeAt( index : Number, array : Array, flag : Boolean )
	{
		var o = array.splice( index, 1 );
		return ( flag ) ? o : array;
	}
	
	/**
	 * Removes the first occurance of the given {@code element} out of the passed-in
	 * {@code array}.
	 * 
	 * @param value Element to remove
	 * @param array {@code Array} instance to search in
	 * @param flag ( optional ) {@code true} to return removed element, 
	 * {@code false} to return source array.
	 */
	public static function removeFirst( value, array : Array, flag : Boolean )
	{
		var l : Number = array.length;
		var index : Number = -1;
		
		for ( var i : Number = 0; i < l; i++ ) 
		{
			if ( array[i] === value )
			{
				array.splice(i, 1);
				index = i;
				break;
			}
		}
		
		return ( flag ) ? index : array;
	}
	
	/**
	 * Removes the last occurance of the given {@code element} out of the passed-in
	 * {@code array}.
	 * 
	 * @param value Element to remove
	 * @param array {@code Array} instance to search in
	 * @param flag ( optional ) {@code true} to return removed element, 
	 * {@code false} to return source array.
	 */
	public static function removeLast( value, array : Array, flag : Boolean )
	{
		var l : Number = array.length;
		var index : Number = -1;
		
		while( --l > - 1 )
		{
			if( array[l] === value )
			{
				array.splice( l, 1 );
				index = l;
				break;
			}
		}
		
		return ( flag ) ? index : array;
	}
	
	/**
	 * Returns index of the passed-in {@code value} element in 
	 * passed-in {@code array}.
	 * 
	 * <p>Returns first index where element is found if many.
	 * 
	 * @param value Value to search for
	 * @param array {@code Array} instance
	 * @return	{@code value} position in {@code array} array (or {@code -1})
	 */
	public static function getIndexOf( value, array : Array ) : Number
	{
		var len : Number = array.length;
		if( !len ) return -1;
		
		var l = -1;
		while( l++ < len ) if( array[l] === value ) return l;
		
		return -1;
	}
	
	/**
	 * Returns index of the passed-in {@code value} element in 
	 * passed-in {@code array}.
	 * 
	 * <p>Returns last index where element is found if many.
	 * 
	 * @param value Value to search for
	 * @param array {@code Array} instance
	 * @return	{@code value} position in {@code array} array (or {@code -1})
	 */
	public static function getLastIndexOf( value, array : Array ) : Number
	{
		var l : Number = array.length;
		if( !l ) return -1;
		
		while( --l > -1 ) if( array[l] === value ) return l;
		
		return -1;
	}
	
	/**
	 * Shuffles the passed-in {@code array}.
	 * 
	 * @param array {@code Array} instance to shuffle
	 */
	public static function shuffle( array : Array ) : Array
	{
		var l : Number = array.length; 
		var r : Number;
		var temp;
		
		for ( var i : Number = l - 1; i >= 0; i-- ) 
		{ 
			r = Math.floor( Math.random() * l ); 
			temp = array[i]; 
			array[i] = array[r]; 
			array[ r ] = temp; 
		} 
		
		return array;
	}
	
	/**
	 * Swaps the value at index {@code i} with the value at index {@code j} of the
	 * passed-in {@code array}.
	 * 
	 * @param array {@code Array} instance
	 * @param i Index of the first value
	 * @param j Index of the second value
	 */
	public static function swap( i : Number, j : Number, array : Array ) : Array
	{
		if( !array || array.length < 2 ) return array;
		
		var l : Number = array.length;
		
		if( i > -1 && i < l && j > -1 && j < l )
		{
			var tmp = array[ i ];
			array[ i ] = array[ j ];
			array[ j ] = tmp;
		}
		
		return array;
	}
	
	/**
	 * Indicates if the 2 passed arrays are equal or not.
	 * 
	 * @param array1 First {@code Array}
	 * @param array1 Second {@code Array}
	 * 
	 * @return {@code true} if 2 arrays are equals, otherwise {@code false}
	 */
	public static function areEqual( array1 : Array, array2 : Array ) : Boolean
	{
		if ( array1.length != array2.length ) return false;
		
		var l : Number = array1.length;
		for ( var i : Number = 0; i < l; i ++ )
		{
			if ( array1 [i] !== array2 [i] ) return false;
		}
		return true;
	}
	
	/**
	 * Makes all array elements unique and returns array.
	 * 
	 * <p>Original array is modified.
	 */
	public static function unique( array : Array ) : Array
	{
		var l : Number = array.length;
		
		for( var i : Number = 0; i < l; i++)
		{
			for( var j : Number = ( i + 1 ); j < l; j++ )
			{
				if( array[i] === array[j] ) array.splice(j, 1);
			}	
		} 
		
		return array;
	}
	
	/**
	 * Returns passed-in {@code a} Array content at random 
	 * index.
	 */
	public static function getRandomSlot( array : Array )
	{
		if( !array || array.length < 1 ) return null;
		
		var index : Number = Random.nextInt( array.length - 1 );
		
		if( array[ index ] == undefined )
		{
			FeverDebug.ERROR( 'Index out of bounds' );
			index = 0;
		}
		
		return array[ index ];
	}
	
	/**
	 * Returns a clone array of passed-in {@code array} instance.
	 */
	public static function clone( array : Array ) : Array
	{
		return array.concat();
	}
	
	/**
	 * Tests all {@code array} elements using {@code func} function in {@code o} 
	 * scope.
	 * 
	 * <p>If all executions returns {@code true} ( and only in this case ) 
	 * then returns {@code true}.
	 * 
	 * <p>Example 1. Using basic call
	 * public function isValid( value, index : Number ) : Boolean
	 * {
	 * 	return value > 20;
	 * }
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * FeverDebug.INFO( ArrayUtil.test( a, isValid, this ) );
	 * //display false;
 	 * 
	 *<p>Example 2. Adding some arguments to use for test
	 * public function isValid( value, index : Number, param : Number ) : Boolean
	 * {
	 * 	return value > param;
	 * }
	 * 
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * FeverDebug.INFO( ArrayUtil.test( a, isValid, this, -1 ) );
	 * //display true;
	 * 
	 * @param array {@code Array} instance to test
	 * @param func Testing function ( must return a boolean )
	 * @param o Scope of function
	 * @param ... Can pass as many more arguments you want
	 * @return {@code true} if all tests are {@code true}
	 */
	public static function test( array : Array, func : Function, o : Object  ) : Boolean 
	{
		var l : Number = array.length ;
		var args : Array = arguments.splice( 3 );
		
		for ( var i : Number = 0 ; i < l ; i++ ) 
		{
			if( !func.apply( o, [ array[i], i ].concat( args ) ) ) return false ;
	    }
		return true ;
    }
	
	/**
	 * Executes {@code func} function in {@code o} scope for all 
	 * {@code array} elements
	 * 
	 * <p>Example 1. Using basic call
	 * public function showMe( value, index : Number ) : Void
	 * {
	 * 	trace( value + ' -> ' + index );
	 * }
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * ArrayUtil.call( a, showMe, this );
	 * 
 	 * 
	 *<p>Example 2. Adding some arguments
	 * public function showMe( value, index : Number, timer : Number ) : Void
	 * {
	 * 	trace( value + ' -> ' + index + ' at ' + timer);
	 * }
	 * 
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * ArrayUtil.call( a, showMe, this, getTimer() );
	 * 
	 * @param array {@code Array} instance
	 * @param func Function to call
	 * @param o Scope of function
	 * @param ... Can pass as many more arguments you want
	 */
	public static function call( array : Array, func : Function, o : Object  ) : Void 
	{
		var l : Number = array.length ;
		var args : Array = arguments.splice( 3 );
		
		for ( var i : Number = 0 ; i < l ; i++ ) 
		{
			func.apply( o, [ array[i], i ].concat( args ) );
	    }
    }
    
    /**
	 * Executes {@code func} function in {@code o} scope for all 
	 * {@code array} elements and replaces element.
	 * 
	 * <p>Example 1. Using basic call
	 * public function transform( value, index : Number ) : Void
	 * {
	 * 	return value + 10;
	 * }
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * ArrayUtil.apply( a, transform, this );
	 * 
 	 * 
	 *<p>Example 2. Adding some arguments to use
	 * public function transform( value, index : Number, delta : Number ) : Void
	 * {
	 * 	return value + delta;
	 * }
	 * 
	 * var a : Array =  new Array( 1, 2, 3, 4, 5, 6, 7, 8, 9 );
	 * ArrayUtil.apply( a, transform, this, 100 );
	 * 
	 * @param array {@code Array} instance
	 * @param func Function to call ( must return a boolean )
	 * @param o Scope of function
	 * @param ... Can pass as many more arguments you want
	 * @return {@code Array} instance
	 */
    public static function apply( array : Array, func : Function, o : Object  ) : Array 
	{
		var l : Number = array.length ;
		var args : Array = arguments.splice( 3 );
		
		for ( var i : Number = 0 ; i < l ; i++ ) 
		{
			array[i] = func.apply( o, [ array[i], i ].concat( args ) );
	    }
	    
	    return array;
    }
    

	//------------------------------------------------------------------//
	// Private implementation
	//------------------------------------------------------------------//
	
	/** Constructor is private to avoid runtime instanciation. **/
	private function ArrayUtil() {}
}